package com.cvext.requiredvalidation.controls.ui;

import javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseId;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

import org.apache.shale.validator.CommonsValidator;

import com.sun.faces.util.Util;

/**
 * @author hturksoy
 * 
 * Component that scans the enclosing UIForm for RequiredValidators and executes them.
 */
public class RequiredValidatorChecker extends ComponentFormWalker {
	
	public static final String COMPONENT_TYPE	=	"cvext.controls.RequiredValidatorChecker";
	
	public static final String COMPONENT_FAMILY	=	"cvext.controls.RequiredValidatorChecker";
	
	public RequiredValidatorChecker()
    {
        super();
    }
    
    public String getFamily()
    {
        return COMPONENT_FAMILY;
    }
    
    public boolean isRendered()
    {
        // a RequiredValidatorChecker is never rendered
        return false;
    }

	protected boolean shouldScanDuringApplyRequestValues(FacesContext context)
    {
		return true;
    }

    protected boolean shouldScanDuringProcessValidations(FacesContext context)
    {
    	return true;
    }
    
    protected void visitComponent(FacesContext context, UIComponent component, PhaseId phase)
    {
        if (!component.isRendered() || Util.componentIsDisabledOrReadonly(component)) return;

        if (false == (component instanceof EditableValueHolder))  return;
        
        EditableValueHolder inputComponent = (EditableValueHolder)component;
        
        if ((inputComponent.isImmediate()) && (phase.getOrdinal() == PhaseId.APPLY_REQUEST_VALUES.getOrdinal()) )
        {
            executeRequiredValidators(context, component, inputComponent);
            return;
        }
        else if ((false == inputComponent.isImmediate()) && (phase.getOrdinal() == PhaseId.PROCESS_VALIDATIONS.getOrdinal()) )
        {
            executeRequiredValidators(context, component, inputComponent);
            return;
        }
    }
    
    protected void executeRequiredValidators(FacesContext context, UIComponent component, EditableValueHolder inputComponent)
    {
        Validator[] validators = inputComponent.getValidators();
        for (int i = 0; i < validators.length; i++)
        {
            Validator currentValidator = validators[i];
            
            if (isRequiredValidator(currentValidator))
            {
                try
                {
                    if (inputComponent.isLocalValueSet())
                    {
                    	Object localValue = inputComponent.getLocalValue();
						if (logger.isDebugEnabled())
                    		logger.debug("executeRequiredValidators : " + component.getId() + ", localvalue : " + localValue);

						/*
						 * commonsvalidator validates component's submittedvalue always (as all validators do).
						 * Normally, validators will be fired at applyrequest phase by jsf. But, requiredvalidatorchecker 
						 * takes place at processValidations phase of itself. In this phase, all components' submittedvalue
						 * will be empty. 
						 * So, we will set localvalue to submittedvalue temporarily and after validation will restore 
						 * original submittedvalue back.
						 */
						Object storedSubmittedValue = ((EditableValueHolder)component).getSubmittedValue();
						((EditableValueHolder)component).setSubmittedValue(localValue);
						// fire validator
						currentValidator.validate(context, component, localValue);
						// restore old submittedvalue back
						((EditableValueHolder)component).setSubmittedValue(storedSubmittedValue);
						
                    }
                    else
                    {
                    	Object submittedValue = inputComponent.getSubmittedValue();
						if (logger.isDebugEnabled())
                    		logger.debug("executeRequiredValidators : " + component.getId() + ", submittedvalue : " + submittedValue);
						// fire validator
                        currentValidator.validate(context, component, submittedValue);
                    }
                }
                catch (ValidatorException e)
                {
                	if (logger.isErrorEnabled())
                		logger.error("validator exception occured : " + e.getFacesMessage() + "\n" + e.getMessage());
                    inputComponent.setValid(false);
                    FacesMessage facesMessage = e.getFacesMessage();
                    if (facesMessage != null)
                    {
                        context.addMessage(component.getClientId(context), facesMessage);
                    }
                    else
                    {
                        //TODO: specification? add a general message?
                    }
                    //TODO: specification? should we abort validation immediately
                }
                if (!inputComponent.isValid())
                {
                    context.renderResponse();
                }
            }
        }
    }
    
    protected boolean isRequiredValidator(Validator currentValidator)
    {
    	boolean required = false;
        if (currentValidator instanceof CommonsValidator)
        {
        	CommonsValidator commonsValidator = (CommonsValidator) currentValidator;
        	required = "required".equals(commonsValidator.getType());
        }
        
        return required;
    }
}
